<template>
    <div class="flex flex-col h-screen">
      <div class="flex flex-nowrap fixed w-full items-baseline top-0 px-6 py-4 bg">
        <div class="text-2xl font-bold">SparkChatGPT</div>
        <el-tag class="ylj" type="success" size="big">已连接</el-tag>
        <div class="ml-4 text-sm text-gray-500">2024.01.26</div>
      </div>
  
      <div class="flex-1 mx-2 mt-20 mb-2 kd" ref="chatListDom">
        <!-- 颜色 -->
        <div
          v-for="(item, key) of messageList.filter((v) => v.role !== 'system')"
          :key="key"
          class="group flex flex-col px-4 py-3 rounded-lg"
        >
          <!-- shadow-md -->
          <div class="flex flex-start items-center mb-2">
            <!-- AI头像 -->
            <div v-if="key % 2 !== 0" style="margin-right: 10px">
              <svg 
                version="1.1" 
                id="Layer_1" 
                xmlns="http://www.w3.org/2000/svg" 
                xmlns:xlink="http://www.w3.org/1999/xlink" 
                x="0px" 
                y="0px" 
                width="32" 
                height="32" 
                viewBox="0 0 128 128" 
                enable-background="new 0 0 128 128" 
                xml:space="preserve"> 
                  <text x="-9999" y="-9999">SparkChatGPT</text>
                  <image id="image0" width="128" height="128" x="0" y="0"
                    href="" ></image>
              </svg>

            </div>
  
            <!-- 用户头像 -->
            <div v-if="key % 2 === 0" style="margin-right: 10px">
              <svg t="1700498026576" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2717" width="32" height="32"><path d="M632 344m-120 0a120 120 0 1 0 240 0 120 120 0 1 0-240 0Z" fill="#A9D8FF" p-id="2718"></path><path d="M809 800H503c-48.1 0-87-39-87-87 0-111 90-201 201-201h78c111 0 201 90 201 201 0 48-39 87-87 87z" fill="#A9D8FF" p-id="2719"></path><path d="M440 296m-168 0a168 168 0 1 0 336 0 168 168 0 1 0-336 0Z" fill="#298DF7" p-id="2720"></path><path d="M638.2 896H241.8C179 896 128 845 128 782.2 128 633 249 512 398.2 512h83.6C631 512 752 633 752 782.2 752 845 701 896 638.2 896z" fill="#298DF7" p-id="2721"></path></svg>
            </div>
  
            <!-- 输入内容 -->
            <div class="bt font-bold" style="font-size: 16px">
              {{ roleAlias[item.role] }}
            </div>
          </div>
          <div>
            <div
              class="text-sm leading-relaxed"
              v-if="item.content"
              v-html="md.render(item.content)"
            ></div>
            <Loading v-else />
            <Copy
              style="margin-top: 10px"
              class="group-hover:visible"
              :content="item.content"
              v-if="key % 2 !== 0"
            />
          </div>
        </div>
      </div>
  
      <div class="sticky bottom-0 w-full p-6 pb-8 kd">
        <div class="flex">
          <el-input
            round
            size="large"
            style="width: 95%; margin-right: 30px"
            v-model="messageContent"
            placeholder="Message..."
            @keydown.enter="sendChatMessage()"
          />

          <el-button
            round
            type="primary"
            size="large"
            @click="sendChatMessage()"
            :loading="isTalking"
            >发 送</el-button
          >
        </div>
      </div>
    </div>
  </template>
  
  <script setup>
  import { ref, watch, nextTick, onMounted } from "vue";
  import Loading from "@/components/Loading.vue";
  import Copy from "@/components/Copy.vue";
  import { md } from "@/libs/markdown";
  import CryptoJS from "crypto-js";
  import * as base64 from "base-64";
  import { ElMessage, ElMessageBox } from "element-plus";
  
  let isTalking = ref(false);
  let messageContent = ref(null);
  const socket = ref(null);
  
  // 自己的ApiKey 3.1 先注册星火大模型，生成APPID、APISecret、APIKey
  const apiData = ref({
    APPID: "18a7987c",
    APISecret: "ZDMwODA0YTc0N2NkNWFjNzg1MTI2NmY3",
    APIKey: "aecc6398a8128bdd538bc176cab2b2ad",
  });

  const chatListDom = ref(null);
  const roleAlias = {
    user: "You",
    assistant: "Spark",
    system: "System",
  };

  const messageList = ref([
    {
      role: "system",
      content: "我是Rain建立的模型，尽可能简洁地回答。",
    },
    {
      role: "assistant",
      content: `How can I help you today?\n\n`
              +`可以向我提问，我会高效便捷地帮助人们获取信息、知识和灵感。`,
    },
  ])
  
  localStorage.setItem("tokenInfo", "woaiwangrui");
  
  const checkTokenInfo = () => {
    return new Promise((resolve, reject) => {
      ElMessageBox.prompt("请输入密钥：", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
      })
        .then(({ value }) => {
          if (value === "woaiwangrui") {
            localStorage.setItem("tokenInfo", "woaiwangrui");
            // @ts-ignore
            resolve();
          } else {
            reject(new Error("校验失败"));
          }
        })
        .catch(() => {
          reject(new Error("校验失败"));
        });
    });
  };
  
  const sendChatMessage = async (content = messageContent.value) => {
    let tokenInfo = localStorage.getItem("tokenInfo");
  
    if (tokenInfo === "woaiwangrui") {
      try {
        if (!messageContent.value.length) return;
  
        isTalking.value = true;
        if (messageList.value.length === 2) {
          messageList.value.pop();
        }
  
        messageList.value.push({ role: "user", content });
        messageList.value.push({ role: "assistant", content: "" });
  
        callSparkModel();
      } catch (error) {
        appendLastMessageContent(error);
      }
    }
  };
  
  // 3.1模型
  const getWebSocketUrl = () => {
    return new Promise((resolve, reject) => {
      let url = "wss://spark-api.xf-yun.com/v3.5/chat";
      let host = "spark-api.xf-yun.com";
      let apiKeyName = "api_key";
     
      let date = new Date().toGMTString();
      let algorithm = "hmac-sha256";
      let headers = "host date request-line";
  
      // 注意这里的只变GET后的版本号 比如：1.1 2.1 3.1 3.5 后面的HTTP不变
      let signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v3.5/chat HTTP/1.1`;
      let signatureSha = CryptoJS.HmacSHA256(
        signatureOrigin,
        apiData.value.APISecret
      );
      let signature = CryptoJS.enc.Base64.stringify(signatureSha);
      let authorizationOrigin = `${apiKeyName}="${apiData.value.APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
      let authorization = base64.encode(authorizationOrigin);
      url = `${url}?authorization=${authorization}&date=${encodeURI(
        date
      )}&host=${host}`;
  
      resolve(url);
    });
  };
  
  const callSparkModel = async () => {
    const url = await getWebSocketUrl();
    if ("WebSocket" in window) {
      socket.value = new WebSocket(url);
    } else if ("MozWebSocket" in window) {
      // @ts-ignore
      socket.value = new MozWebSocket(url);
    } else {
      alert("暂时不支持websocket");
      return null;
    }
  
    socket.value.onopen = (e) => {
      let params = {
        header: {
          app_id: apiData.value.APPID
        },
        parameter: {
          // 3.1 参数
          chat: {
            domain: "generalv3",
            temperature: 0.5,
            max_tokens: 1024 * 3,
          },
        },
        payload: {
          message: {
            text: [
              {
                role: "user",
                content: messageContent.value,
              },
            ],
          },
        },
      };
  
      socket.value.send(JSON.stringify(params));
      clearMessageContent();
    };
  
    socket.value.onmessage = (e) => {
      let obj = JSON.parse(e.data);
      let dataArray = obj.payload.choices.text;
  
      for (let i = 0; i < dataArray.length; i++) {
        appendLastMessageContent(dataArray[i].content);
      }
  
      let temp = JSON.parse(e.data);
      if (temp.header.code !== 0) {
        isTalking.value = false;
        socket.value.close();
      }
      if (temp.header.code === 0) {
        if (e.data && temp.header.status === 2) {
          setTimeout(() => {
            isTalking.value = false;
            socket.value.close();
          }, 1000);
        }
      }
    };
  };
  
  const appendLastMessageContent = (content) => {
    messageList.value[messageList.value.length - 1].content += content;
  };
  
  const clearMessageContent = () => {
    messageContent.value = "";
  };
  
  const scrollToBottom = () => {
    if (!chatListDom.value) return;
    scrollTo(0, chatListDom.value.scrollHeight);
  };
  
  watch(messageList.value, () => nextTick(() => scrollToBottom()));
  </script>
  
  <style scoped>
  pre {
    font-family: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica,
      "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB",
      "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
      "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti",
      SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;
  }
  .prose
    :where(ol > li):not(
      :where([class~="not-prose"], [class~="not-prose"] *)
    )::marker {
    color: #ffffff;
  }
  
  .bt {
    display: flex;
  }
  
  .kd {
    margin: auto;
    margin-top: 60px;
    width: 550px;
    /* margin-left: 300px ;
    margin-right: 300px ; */
  
  }
  
  @media (max-width: 767px) {
    .kd {
      /* margin: auto; */
      width: 390px;
    }
  }
  
  .bg{
    backdrop-filter: blur(20px); /* 调整模糊程度，单位可以是像素(px)或其他合适的值 */
  }
  
  .ylj{
    translate:  2px -10px;
  }
  </style>
  